package containers

import (
	"com.yy.api/common/logger"
	"com.yy.api/common/utils"
	"context"
	"fmt"
	nettypes "github.com/containers/common/libnetwork/types"
	containers_gb "github.com/containers/podman/v4/pkg/bindings/containers"
	"github.com/containers/podman/v4/pkg/domain/entities"
	"github.com/containers/podman/v4/pkg/specgen"
	spec "github.com/opencontainers/runtime-spec/specs-go"
	"os"
)

// rules

// configs
type strMap struct {
	tag string
	val string
}

const (
	NVIDIA_GPU_PATH     string = "/dev/nvidia"
	NVIDIA_MEM_PATH     string = "/dev/nvidia-uvm"
	NVIDIA_CTL_PATH     string = "/dev/nvidiactl"
	NVIDIA_DRIVER_PATH  string = "/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.550.144.03"
	NVIDIA_TOOLKIT_PATH string = "/usr/bin/nvidia-smi"
	NVIDIA_DEVICE_ID    int64  = 195
	DEFAULT_UID         uint32 = 1000
	DEFAULT_GID         uint32 = 1000
)

var (
	fileMode = os.FileMode(0660)
)

func (sm *strMap) String() string {
	return sm.tag + "=" + sm.val
}

type basic struct {
	tempEnv    []strMap
	tempLabels []strMap
	Labels     *map[string]string
	Env        *map[string]string
}

func (b *basic) addLabels() {

	labels := make(map[string]string)
	for _, l := range b.tempLabels {
		labels[l.tag] = l.val
	}
	logger.Default.Infof("%v", labels)
	b.Labels = &labels
}

func (b *basic) addEnvs() {

	envs := make(map[string]string)
	for _, e := range b.tempEnv {
		envs[e.tag] = e.val
	}
	logger.Default.Infof("%v", envs)
	b.Env = &envs
}

type network struct {
	tempPortMappings []nettypes.PortMapping
	PortMappings     *[]nettypes.PortMapping
}

func (n *network) addPortMappings() {
	logger.Default.Infof("%v", n.tempPortMappings)
	n.PortMappings = &n.tempPortMappings
}

type storage struct {
	tempMounts  []spec.Mount
	Mounts      *[]spec.Mount
	tempDevices []spec.LinuxDevice
	Devices     *[]spec.LinuxDevice
}

func (s *storage) addMounts() {
	logger.Default.Infof("%v", s.tempMounts)
	s.Mounts = &s.tempMounts
}

func (s *storage) addDevices() {
	logger.Default.Infof("%v", s.tempDevices)
	s.Devices = &s.tempDevices
}

// Generator
type CreateSpec struct {
	spec     *specgen.SpecGenerator
	*basic   // 添加labels 便于查询
	*network // 配置端口映射
	// *resource //
	*storage // 配置存储,挂载,设备等
	// *security
}

func NewCreateSpec(rowImage string) *CreateSpec {
	return &CreateSpec{
		spec: specgen.NewSpecGenerator(rowImage, false),
		basic: &basic{
			tempLabels: []strMap{},
			tempEnv:    []strMap{},
		},
		network: &network{
			tempPortMappings: []nettypes.PortMapping{},
		},
		//		security: &security{},
		storage: &storage{
			tempMounts:  make([]spec.Mount, 0),
			tempDevices: make([]spec.LinuxDevice, 0),
		},
		//		resource: &resource{},
	}
}
func (cs *CreateSpec) AddLabel(tag, val string) {
	cs.basic.tempLabels = append(cs.basic.tempLabels, strMap{tag, val})
}
func (cs *CreateSpec) AddEnv(tag, val string) {
	cs.basic.tempEnv = append(cs.basic.tempEnv, strMap{tag, val})
}
func (cs *CreateSpec) AddPortMapping(hostPort, containerPort string) {
	hp := utils.ConvStrToUint16(hostPort)
	cp := utils.ConvStrToUint16(containerPort)
	cs.network.tempPortMappings = append(cs.network.tempPortMappings, nettypes.PortMapping{
		HostPort:      hp,
		ContainerPort: cp,
	})
	cs.AddLabel(hostPort, "used")
}
func (cs *CreateSpec) AddMount(dest, src, fsType string, options []string) {
	cs.storage.tempMounts = append(cs.storage.tempMounts, spec.Mount{
		Destination: dest,
		Type:        fsType,
		Source:      src,
		Options:     options,
	})
	// TODO 根据条件自动添加标签
	if false {
		// cs.AddLabel(src, dest)
	}
}

func (cs *CreateSpec) AddDevice(devicePath, deviceType string, maj, min int64, fileMode *os.FileMode, uid, gid *uint32) {
	cs.storage.tempDevices = append(cs.storage.tempDevices, spec.LinuxDevice{
		Path:     devicePath,
		Type:     deviceType,
		Major:    maj,
		Minor:    min,
		FileMode: fileMode,
		UID:      uid,
		GID:      gid,
	})
}
func (cs *CreateSpec) AddNvidiaGpuDevice(gpuId int) {
	devicePath := fmt.Sprintf("%s%d", NVIDIA_GPU_PATH, gpuId)
	cs.AddDevice(devicePath, "c", NVIDIA_DEVICE_ID, int64(gpuId+1), &fileMode, utils.Uint32ToPtr(DEFAULT_UID), utils.Uint32ToPtr(DEFAULT_GID))
	cs.AddLabel(devicePath, "used")
}

func (cs *CreateSpec) generate() *specgen.SpecGenerator {
	cs.push()
	cs.assemble()
	return cs.spec
}

func (cs *CreateSpec) push() {
	cs.addPortMappings()
	cs.addLabels()
	cs.addEnvs()
	cs.addDevices()
	cs.addMounts()
}
func (cs *CreateSpec) assemble() {
	if len(*cs.PortMappings) != 0 {
		for _, pm := range *cs.PortMappings {
			cs.spec.PortMappings = append(cs.spec.PortMappings, pm)
		}
	}
	if len(*cs.Labels) != 0 {
		cs.spec.Labels = *cs.Labels
	}
	if len(*cs.Env) != 0 {
		cs.spec.Env = *cs.Env
	}
	if len(*cs.Mounts) != 0 {
		for _, m := range *cs.Mounts {
			cs.spec.Mounts = append(cs.spec.Mounts, m)
		}
	}
	if len(*cs.Devices) != 0 {
		for _, d := range *cs.Devices {
			cs.spec.Devices = append(cs.spec.Devices, d)
		}
	}
	logger.Default.Infof("%v", cs.spec)
}

// function CreateWithSpec
func (cs *CreateSpec) CreateWithSpec(ctx context.Context) (entities.ContainerCreateResponse, error) {
	return containers_gb.CreateWithSpec(ctx, cs.generate(), nil)
}

func (cs *CreateSpec) AddNvidiaBasicEnv() {
	cs.AddEnv("NVIDIA_VISIBLE_DEVICES", "all")
	cs.AddEnv("NVIDIA_DRIVER_CAPABILITIES", "compute,utility")
	cs.AddDevice(NVIDIA_CTL_PATH, "c", NVIDIA_DEVICE_ID, int64(255), &fileMode, utils.Uint32ToPtr(DEFAULT_UID), utils.Uint32ToPtr(DEFAULT_GID))
	cs.AddDevice(NVIDIA_MEM_PATH, "c", NVIDIA_DEVICE_ID, int64(0), &fileMode, utils.Uint32ToPtr(DEFAULT_UID), utils.Uint32ToPtr(DEFAULT_GID))
	cs.AddMount(NVIDIA_TOOLKIT_PATH, NVIDIA_TOOLKIT_PATH, "bind", []string{"ro", "bind"})
	cs.AddMount(NVIDIA_DRIVER_PATH, NVIDIA_DRIVER_PATH, "bind", []string{"ro", "bind"})
}
